<?php
/***********************************************************************
 * @package    	IvanEngine
 * @subpackage 	Core
 * @author     	Ivan <ivanzx@msn.com>
 * @created    	2010-05-12
 * @purpose		It also implements the required option for all validators.
 ***********************************************************************/
if(defined("IVANENGINE_CORE_VALIDATOR_BASE"))
	die("Redefinition macro in :".__FILE__."(".__LINE__.")");
define("IVANENGINE_CORE_VALIDATOR_BASE", true);

require_once (dirname(__FILE__).'/../Common.php');
require_once (dirname(__FILE__).'/../Exception/ExceptionValidator.php');

abstract class CValidatorBase extends CObject
{
	protected static
	    $m_charset         	= 'UTF-8',
	    $m_invalidMsg  		= 'Invalid.',
	    $m_requiredMsg 		= 'Required.';

  	protected
	    $m_requiredOptions 	= array(),
	    $m_defaultMessages 	= array(),
	    $m_defaultOptions  	= array(),
	    $m_messages        	= array(),
	    $m_options         	= array();
    
	// normal option
	const NO_Required		= 'NO_Required';
	const NO_Trim			= 'NO_Trim';
	const NO_EmptyValue		= 'NO_EmptyValue';
	
	// normal message
	const NM_Required		= 'NM_Required';
	const NM_Invalid		= 'NM_Invalid';
	
	/**
   	* Constructor.
   	*
   	* Available m_options:
   	*
   	*  * required:    true if the value is required, false otherwise (default to true)
   	*  * trim:        true if the value must be trimmed, false otherwise (default to false)
   	*  * empty_value: empty value when value is not required
   	*
   	* Available error codes:
   	*
   	*  * required
   	*  * invalid
   	*
   	* @param array $m_options   An array of m_options
   	* @param array $m_messages  An array of error m_messages
   	*/
	public function __construct($m_options = array(), $m_messages = array())
	{
		parent::__construct ();
		$this->m_options  = array_merge(array(	self::NO_Required 	=> true, 
												self::NO_Trim 		=> false, 
												self::NO_EmptyValue	=> null), 
												$this->m_options);
												
	    $this->m_messages = array_merge(array(	self::NM_Required 	=> self::$m_requiredMsg, 
	    										self::NM_Invalid 	=> self::$m_invalidMsg), 
	    										$this->m_messages);
	
	    $this->Initialize($m_options, $m_messages);
	
	    $this->setDefaultOptions($this->getOptions());
	    $this->setDefaultMessages($this->getMessages());
	
	    $currentOptionKeys = array_keys($this->m_options);
	    $optionKeys = array_keys($m_options);
		
	    $diff	= array_diff($optionKeys, array_merge($currentOptionKeys, $this->m_requiredOptions));
	    // check option names
	    if (!empty($diff))
	    {
	      	throw new CExceptionInvalidArgument('%s does not support the following options: \'%s\'.', get_class($this), implode('\', \'', $diff));
	    }
	
	    // check error code names
	    $diff	= array_diff(array_keys($m_messages), array_keys($this->m_messages));
	    if (!empty($diff))
	    {
	      	throw new CExceptionInvalidArgument('%s does not support the following error codes: \'%s\'.', get_class($this), implode('\', \'', $diff));
	    }
	
	    // check required m_options
	    $diff	= array_diff($this->m_requiredOptions, array_merge($currentOptionKeys, $optionKeys));
	    if (!empty($diff))
	    {
	      	throw new CExceptionRuntime('%s requires the following options: \'%s\'.', get_class($this), implode('\', \'', $diff));
	    }
	
	    $this->m_options  = array_merge($this->m_options, $m_options);
	    $this->m_messages = array_merge($this->m_messages, $m_messages);
	}
	
	function __destruct()
	{
		parent::__destruct();
	}
	
	/**
   * Configures the current validator.
   *
   * This method allows each validator to add m_options and error m_messages
   * during validator creation.
   *
   * If some m_options and m_messages are given in the sfValidatorBase constructor
   * they will take precedence over the m_options and m_messages you configure
   * in this method.
   *
   * @param array $m_options   An array of m_options
   * @param array $m_messages  An array of error m_messages
   *
   * @see __construct()
   */
	protected function Initialize($options = array(), $messages = array())
  	{
  	}
  	
	/**
   	* Returns an error message given an error code.
   	*
   	* @param  string $name  The error code
   	*
   	* @return string The error message, or the empty string if the error code does not exist
   	*/
  	public function GetMessage($name)
  	{
    	return isset($this->m_messages[$name]) ? $this->m_messages[$name] : '';
  	}

  	/**
   	* Adds a new error code with a default error message.
   	*
   	* @param string $name   The error code
   	* @param string $value  The error message
   	*/
  	public function AddMessage($name, $value)
  	{
    	$this->m_messages[$name] = $value;
  	}

  	/**
   	* Changes an error message given the error code.
   	*
   	* @param string $name   The error code
   	* @param string $value  The error message
   	*/
  	public function SetMessage($name, $value)
  	{
    	if (!in_array($name, array_keys($this->m_messages)))
    	{
      		throw new CExceptionInvalidArgument('%s does not support the following error code: \'%s\'.', get_class($this), $name);
    	}

    	$this->m_messages[$name] = $value;
  	}

  	/**
   	* Returns an array of current error m_messages.
   	*
   	* @return array An array of m_messages
   	*/
  	public function GetMessages()
  	{
    	return $this->m_messages;
  	}

  	/**
   	* Changes all error m_messages.
   	*
   	* @param array $values  An array of error m_messages
   	*/
  	public function setMessages($values)
  	{
    	$this->m_messages = $values;
  	}

  	/**
   	* Gets an option value.
   	*
   	* @param  string $name  The option name
   	*
   	* @return mixed  The option value
   	*/
  	public function GetOption($name)
  	{
    	return isset($this->m_options[$name]) ? $this->m_options[$name] : null;
  	}

  	/**
   	* Adds a new option value with a default value.
   	*
   	* @param string $name   The option name
   	* @param mixed  $value  The default value
   	*/
  	public function AddOption($name, $value = null)
  	{
    	$this->m_options[$name] = $value;
  	}

  	/**
   	* Changes an option value.
   	*
   	* @param string $name   The option name
   	* @param mixed  $value  The value
   	*/
  	public function SetOption($name, $value)
  	{
    	if (!in_array($name, array_merge(array_keys($this->m_options), $this->m_requiredOptions)))
    	{
      		throw new CExceptionInvalidArgument('%s does not support the following option: \'%s\'.', get_class($this), $name);
    	}

    	$this->m_options[$name] = $value;
  	}

  	/**
   	* Returns true if the option exists.
   	*
   	* @param  string $name  The option name
   	*
   	* @return bool true if the option exists, false otherwise
   	*/
  	public function HasOption($name)
  	{
    	return isset($this->m_options[$name]);
  	}

  	/**
   	* Returns all m_options.
   	*
   	* @return array An array of m_options
   	*/
  	public function GetOptions()
  	{
    	return $this->m_options;
  	}

  	/**
   	* Changes all m_options.
   	*
   	* @param array $values  An array of m_options
   	*/
  	public function setOptions($values)
  	{
    	$this->m_options = $values;
  	}

  	/**
   	* Adds a required option.
   	*
   	* @param string $name  The option name
   	*/
  	public function AddRequiredOption($name)
  	{
    	$this->m_requiredOptions[] = $name;
  	}

  	/**
   	* Returns all required option names.
   	*
   	* @param array An array of required option names
   	*/
  	public function GetRequiredOptions()
  	{
    	return $this->m_requiredOptions;
  	}

  	/**
   	* Sets the default invalid message
   	*
   	* @param string $message
   	*/
  	static public function SetInvalidMessage($message)
  	{
    	self::$m_invalidMsg = $message;
  	}

  	/**
   	* Sets the default required message
   	*
   	* @param string $message
   	*/
  	static public function SetRequiredMessage($message)
  	{
    	self::$m_requiredMsg = $message;
  	}

  	/**
   	* Cleans the input value.
   	*
   	* This method is also responsible for trimming the input value
   	* and checking the required option.
   	*
   	* @param  mixed $value  The input value
   	*
   	* @return mixed The cleaned value
   	*
   	* @throws CValidatorException
   	*/
  	public function Verify($value)
  	{
    	$valid = $value;

    	if ($this->m_options[self::NO_Trim] && is_string($valid))
    	{
      		$valid = trim($valid);
    	}

    	// empty value?
    	if ($this->IsEmpty($valid))
    	{
      		// required?
      		if ($this->m_options[self::NO_Required])
      		{
        		throw new CExceptionValidator($this, self::NO_Required);
      		}

      		return $this->GetEmptyValue();
    	}

    	return $this->DoVerify($valid);
  	}

  	/**
   	* Cleans the input value.
   	*
   	* Every subclass must implements this method.
   	*
   	* @param  mixed $value  The input value
   	*
   	* @return mixed The cleaned value
   	*
   	* @throws CValidatorException
   	*/
  	abstract protected function DoVerify($value);

  	/**
   	* Sets the charset to use when validating strings.
   	*
   	* @param string $m_charset  The charset
   	*/
  	static public function SetCharset($m_charset)
  	{
    	self::$m_charset = $m_charset;
  	}

  	/**
   	* Returns the charset to use when validating strings.
   	*
   	* @return string The charset (default to UTF-8)
   	*/
  	static public function GetCharset()
  	{
    	return self::$m_charset;
  	}

  	/**
   	* Returns true if the value is empty.
   	*
   	* @param  mixed $value  The input value
   	*
   	* @return bool true if the value is empty, false otherwise
   	*/
  	protected function IsEmpty($value)
  	{
    	return in_array($value, array(null, '', array()), true);
  	}

  	/**
   	* Returns an empty value for this validator.
   	*
   	* @return mixed The empty value for this validator
   	*/
  	protected function GetEmptyValue()
  	{
    	return $this->GetOption(self::NO_EmptyValue);
  	}

  	/**
   	* Returns an array of all error codes for this validator.
   	*
   	* @return array An array of possible error codes
   	*
   	* @see GetDefaultMessages()
   	*/
  	final public function GetErrorCodes()
  	{
    	return array_keys($this->GetDefaultMessages());
  	}

  	/**
   	* Returns default m_messages for all possible error codes.
   	*
   	* @return array An array of default error codes and m_messages
   	*/
  	public function GetDefaultMessages()
  	{
    	return $this->m_defaultMessages;
  	}

  	/**
   	* Sets default m_messages for all possible error codes.
   	*
   	* @param array $m_messages  An array of default error codes and m_messages
   	*/
  	protected function SetDefaultMessages($m_messages)
  	{
    	$this->m_defaultMessages = $m_messages;
  	}

  	/**
   	* Returns default option values.
   	*
   	* @return array An array of default option values
   	*/
  	public function GetDefaultOptions()
  	{
    	return $this->m_defaultOptions;
  	}

  	/**
   	* Sets default option values.
   	*
   	* @param array $m_options  An array of default option values
   	*/
  	protected function SetDefaultOptions($m_options)
  	{
    	$this->m_defaultOptions = $m_options;
  	}

  	/**
   	* Returns a string representation of this validator.
   	*
   	* @param  int $indent  Indentation (number of spaces before each line)
   	*
   	* @return string The string representation of the validator
   	*/
  	public function AsString($indent = 0)
  	{
    	$m_options = $this->GetOptionsWithoutDefaults();
    	$m_messages = $this->GetMessagesWithoutDefaults();

    	return sprintf('%s%s(%s%s)',
      	str_repeat(' ', $indent),
      	str_replace('CValidator', '', get_class($this)),
      	$m_options ? $m_options : ($m_messages ? '{}' : ''),
      	$m_messages ? ', '.$m_messages : ''
    	);
  	}

  	/**
   	* Returns all error m_messages with non default values.
   	*
   	* @return string A string representation of the error m_messages
   	*/
  	protected function GetMessagesWithoutDefaults()
  	{
    	$m_messages = $this->m_messages;

    	// remove default option values
    	foreach ($this->GetDefaultMessages() as $key => $value)
    	{
      	if (array_key_exists($key, $m_messages) && $m_messages[$key] === $value)
      	{
        	unset($m_messages[$key]);
      	}
    	}

    	return $m_messages;
  	}

  	/**
   	* Returns all m_options with non default values.
   	*
   	* @return string  A string representation of the m_options
   	*/
  	protected function GetOptionsWithoutDefaults()
  	{
    	$m_options = $this->m_options;

    	// remove default option values
    	foreach ($this->GetDefaultOptions() as $key => $value)
    	{
      		if (array_key_exists($key, $m_options) && $m_options[$key] === $value)
      		{
        		unset($m_options[$key]);
      		}
    	}

    	return $m_options;
  	}
}
?>